package com.jackstraw.jack_base.datasource.redis.bean;


import com.jackstraw.jack_base.datasource.common.entity.AbstractPropertiesEntity;
import com.jackstraw.jack_base.datasource.common.entity.enums.RedisTypeEnum;
import com.jackstraw.jack_base.util.BeanUtil;
import com.jackstraw.jack_base.util.CollectionUtil;
import com.jackstraw.jack_base.util.ObjectUtil;
import com.jackstraw.jack_base.util.StringUtil;
import com.jackstraw.jack_base.util.exception.RedisUrlSyntaxException;
import lombok.Data;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.JedisPoolConfig;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;


/**
 * redis连接配置
 */
@Data
public class RedisPropertiesEntity extends AbstractPropertiesEntity<RedisPropertiesEntity> {
    /**
     * 连接模式（默认单机）
     */
    private RedisTypeEnum redisTypeEnum = RedisTypeEnum.Single;

    /**
     * Database index used by the connection factory.
     */
    private int database = 0;

    /**
     * Connection URL. Overrides host, port, and password. User is ignored. Example:
     * redis://user:password@example.com:6379
     */
    private String url;

    /**
     * 解析url
     */
    private ConnectionInfo connectionInfo;

    /**
     * Redis server host.
     */
    private String host = "localhost";

    /**
     * Login username of the redis server.
     */
    private String username;

    /**
     * Login password of the redis server.
     */
    private String password;

    /**
     * Redis server port.
     */
    private int port = 6379;

    /**
     * Whether to enable SSL support.
     */
    private boolean ssl;

    /**
     * 读取超时时间
     */
    private Duration timeout;

    /**
     * 连接超时时间
     */
    private Duration connectTimeout;

    /**
     * Client name to be set on connections with CLIENT SETNAME.
     */
    private String clientName;

    /**
     * Name of the Redis server.
     */
    private String master;
    /**
     * 设置ttl以申请缓存条目。
     */
    private Integer seconds = 300;

    /**
     * Comma-separated list of "host:port" pairs.
     */
    private List<String> nodes;


    /**
     * 线程池设置
     */
    private GenericObjectPoolConfig genericObjectPoolConfig;


    /**
     * 要遵循的最大重定向数。
     */
    private Integer maxRedirects = 0;

    /**
     * 连接池中最小的空闲连接数(默认0)
     */
    public Integer getMinIdle() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getMinIdle();
        }
        return null;
    }

    /**
     * 连接池的最大数据库连接数
     */
    public Integer getMaxIdle() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getMaxIdle();
        }
        return null;
    }

    /**
     * 连接池的最大数据库连接数
     */
    public Integer getMaxTotal() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getMaxTotal();
        }
        return null;
    }

    /**
     * 最大建立连接等待时间
     */
    public Long getMaxWaitMillis() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getMaxWaitMillis();
        }
        return null;
    }

    /**
     * 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
     */
    public Long getMinEvictableIdleTimeMillis() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getMinEvictableIdleTimeMillis();
        }
        return null;
    }

    /**
     * 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
     */
    public Integer getNumTestsPerEvictionRun() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getNumTestsPerEvictionRun();
        }
        return null;
    }

    /**
     * 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
     */
    public Long getTimeBetweenEvictionRunsMillis() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getTimeBetweenEvictionRunsMillis();
        }
        return null;
    }

    /**
     * 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
     */
    public Boolean getTestOnBorrow() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getTestOnBorrow();
        }
        return null;
    }

    /**
     * 在空闲时检查有效性, 默认false
     */
    public Boolean getTestWhileIdle() {
        if (ObjectUtil.ObjectUtilByLang3.isNotEmpty(this.getGenericObjectPoolConfig())) {
            return this.getGenericObjectPoolConfig().getTestWhileIdle();
        }
        return null;
    }


    public String getHost() {
        this.getConnectionInfo();
        return host;
    }

    public String getAddress() {
        return this.getHost().concat(":").concat(String.valueOf(this.getPort()));
    }

    public String getAddress(String prefix) {
        return prefix.concat(this.getHost().concat(":").concat(String.valueOf(this.getPort())));
    }

    public List<String> getNodes() {
        return nodes;
    }

    public List<String> getNodes(String prefix) {
        List<String> nodeListNew = new ArrayList<>();
        if (CollectionUtil.isEmpty(this.getNodes())) {
            return nodeListNew;
        }
        this.getNodes().stream().forEach(node -> nodeListNew.add(prefix.concat(node)));
        return nodeListNew;
    }


    public String getUsername() {
        this.getConnectionInfo();
        return username;
    }

    public String getPassword() {
        this.getConnectionInfo();
        return password;
    }

    public int getPort() {
        this.getConnectionInfo();
        return port;
    }

    public ConnectionInfo getConnectionInfo() {
        if (StringUtil.StringUtilsBylang3.isNotEmpty(this.getUrl())) {
            ConnectionInfo connectionInfo = parseUrl(this.getUrl());
            this.setConnectionInfo(connectionInfo);
            this.setHost(connectionInfo.getHostName());
            this.setPort(connectionInfo.getPort());
            this.setUsername(connectionInfo.getUsername());
            this.setPassword(connectionInfo.getPassword());
            this.setSsl(connectionInfo.useSsl);
            this.setUrl(null);
        }
        return connectionInfo;
    }

    public JedisPoolConfig getJedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        BeanUtil.BeanUtilByHutool.copyProperties(jedisPoolConfig, this.getGenericObjectPoolConfig());
        return jedisPoolConfig;
    }

    /**
     * Shutdown timeout.
     */
    private Duration shutdownTimeout = Duration.ofMillis(100);

    private final Refresh refresh = new Refresh();

    public static ConnectionInfo parseUrl(String url) {
        try {
            URI uri = new URI(url);
            String scheme = uri.getScheme();
            if (!"redis".equals(scheme) && !"rediss".equals(scheme)) {
                throw new RedisUrlSyntaxException(url);
            }
            boolean useSsl = ("rediss".equals(scheme));
            String username = null;
            String password = null;
            if (uri.getUserInfo() != null) {
                String candidate = uri.getUserInfo();
                int index = candidate.indexOf(':');
                if (index >= 0) {
                    username = candidate.substring(0, index);
                    password = candidate.substring(index + 1);
                } else {
                    password = candidate;
                }
            }
            return new ConnectionInfo(uri, useSsl, username, password);
        } catch (URISyntaxException ex) {
            throw new RedisUrlSyntaxException(url, ex);
        }
    }

    @Data
    public static class ConnectionInfo {

        private URI uri;

        private boolean useSsl;

        private String username;

        private String password;

        public ConnectionInfo(URI uri, boolean useSsl, String username, String password) {
            uri = uri;
            useSsl = useSsl;
            username = username;
            password = password;
        }

        public boolean isUseSsl() {
            return useSsl;
        }

        public String getHostName() {
            return uri.getHost();
        }

        public int getPort() {
            return uri.getPort();
        }

        public String getUsername() {
            return username;
        }

        public String getPassword() {
            return password;
        }
    }

    public static class Refresh {

        /**
         * Whether to discover and query all cluster nodes for obtaining the
         * cluster topology. When set to false, only the initial seed nodes are
         * used as sources for topology discovery.
         */
        private boolean dynamicRefreshSources = true;

        /**
         * Cluster topology refresh period.
         */
        private Duration period;

        /**
         * Whether adaptive topology refreshing using all available refresh
         * triggers should be used.
         */
        private boolean adaptive;

        public boolean isDynamicRefreshSources() {
            return this.dynamicRefreshSources;
        }

        public void setDynamicRefreshSources(boolean dynamicRefreshSources) {
            this.dynamicRefreshSources = dynamicRefreshSources;
        }

        public Duration getPeriod() {
            return this.period;
        }

        public void setPeriod(Duration period) {
            this.period = period;
        }

        public boolean isAdaptive() {
            return this.adaptive;
        }

        public void setAdaptive(boolean adaptive) {
            this.adaptive = adaptive;
        }

    }
}